package io.gitee.falllee.dao.util;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringJoiner;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import io.gitee.falllee.dao.config.DaoConfigContextHolder;
import io.gitee.falllee.dao.enums.PlusEnum;
import io.gitee.falllee.dao.model.BaseDaoConfig;
import io.gitee.falllee.dao.model.DaoConst;
import io.gitee.falllee.dao.model.FormatConstant;

/**
 * SQL条件工具类
 * @version 1.0
 * @date 2022/05/09 17:51
 * @author batty
 */
public class FilterPlus {
    
    private FilterPlus() {}
    
    /**
     * 过滤条件
     */
    private StringJoiner filter;
    
    /**
     * 连接符
     */
    private PlusEnum connector;
    
    /**
     * 参数map
     */
    private Map<String, Object> map;
    
    /**
     * 初始化 默认 and 连接
     * 
     * @return
     */
    public static FilterPlus build() {
        return build(PlusEnum.AND);
    }
    
    /**
     * 初始化
     * 
     * @param connector 连接符
     * @return
     */
    public static FilterPlus build(PlusEnum connector) {
        return new FilterPlus(connector);
    }

    /**
     * 获取参数化条件
     * 
     * @return
     */
    public String getParamFilter() {
        String str = filter.toString();
        return DaoConst.BRACKET.equals(str) ? "" : str;
    }

    /**
     * 获取条件
     * 
     * @return
     */
    public String getFilter() {
        if (ObjectUtils.isEmpty(map)) {
            return "";
        }
        String str = filter.toString();
        StringBuilder sb = new StringBuilder(str);
        Iterator<Entry<String, Object>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, Object> entry = iterator.next();
            String parName = String.format(DaoConst.FORMAT_PARAM, entry.getKey());
            int start = sb.indexOf(parName);
            if (start == -1) {
                continue;
            }
            Object objVal = entry.getValue();
            String val;
            if (ObjectUtils.isEmpty(objVal)) {
                val = "null";
            } else {
                val = String.valueOf(objVal);
            }
            int end = start + parName.length();
            sb.replace(start, end, val);
        }
        return sb.toString();
    }
    
    public Map<String, Object> getParamMap() {
        return map;
    }
    
    /**
     * or增加括号，and不变
     * 
     * @return
     */
    public FilterPlus addBrackets() {
        if (PlusEnum.AND == connector) {
            return this;
        }
        String filterStr = getParamFilter();
        if (!StringUtils.hasLength(filterStr.replace("(", "").replace(")", ""))) {
            return this;
        }
        this.filter = initFilter(connector);
        // 由于or连接符会自动添加括号，这里直接添加
        filter.add(filterStr);
        return this;
    }

    private FilterPlus(PlusEnum connector) {
        this.connector = connector;
        this.filter = initFilter(connector);
        this.map = new HashMap<>(16);
    }

    private StringJoiner initFilter(PlusEnum connector) {
        if (PlusEnum.OR == connector) {
            return new StringJoiner(connector.getConnector(), DaoConst.LEFT_BRACKET, DaoConst.RIGHT_BRACKET);
        } else {
            return new StringJoiner(connector.getConnector());
        }
    }
    
    /**
     * if (expression) {
     *     do something
     * }
     * 
     * @param expression
     * @param customer
     * @return
     */
    public FilterPlus addIf(boolean expression, Consumer<FilterPlus> customer) {
        if (expression) {
            customer.accept(this);
        }
        return this;
    }
    
    /**
     * if (expression) {
     *     do something
     * } else {
     *     do something
     * }
     * 
     * @param expression
     * @param ifCustomer
     * @param elseCustomer
     * @return
     */
    public FilterPlus addIfElse(boolean expression, Consumer<FilterPlus> ifCustomer,
            Consumer<FilterPlus> elseCustomer) {
        if (expression) {
            ifCustomer.accept(this);
        } else {
            elseCustomer.accept(this);
        }
        return this;
    }
    
    /**
     * add(p-> {
     *     p.addEquals(a, 1);
     *     p.addEquals(b, 2);
     * });
     * 
     * a = 1 and b = 2
     * 
     * @param customer
     * @return
     */
    public FilterPlus add(Consumer<FilterPlus> customer) {
        return add(customer, PlusEnum.AND);
    }

    /**
     * add(p-> {
     *     p.addEquals(a, 1);
     *     p.addEquals(b, 2);
     * }, " or ");
     * 
     * (a = 1 or b = 2)
     * 
     * @param customer
     * @param connector
     * @return
     */
    public FilterPlus add(Consumer<FilterPlus> customer, PlusEnum connector) {
        FilterPlus subFilter = FilterPlus.build(connector);
        customer.accept(subFilter);
        String paramFilter = subFilter.getParamFilter();
        if (StringUtils.hasLength(paramFilter.replace("(", "").replace(")", ""))) {
            filter.add(paramFilter);
            map.putAll(subFilter.getParamMap());
        }
        return this;
    }
    
    /**
     * eg1: a = 1 and b = 2 and (c = 3 or d = 4)
     * eg2: a = 1 or b = 2 or (c = 3 or d = 4)
     * 
     * @param filterPlus
     * @return
     */
    public FilterPlus join(FilterPlus filterPlus) {
        String paramFilter = filterPlus.getParamFilter();
        if (StringUtils.hasLength(paramFilter.replace("(", "").replace(")", ""))) {
            filter.add(paramFilter);
            map.putAll(filterPlus.getParamMap());
        }
        return this;
    }
    
    private String getParamName() {
        return String.format("P_%s", CommonUtils.getGuid().replace("-", ""));
    }

    private void addFilter(StringJoiner sjFilter, String column, Object value, String format) {
        String paramName = getParamName();
        String parName = String.format(DaoConst.FORMAT_PARAM, paramName);
        sjFilter.add(String.format(format, column, parName));
        map.put(paramName, value);
    }

    private void addFilter(String column, Object value, String format) {
        addFilter(filter, column, value, format);
    }
    
    /**
     * column = 'value'
     */
    public FilterPlus addEquals(String column, Object value) {
        if (ObjectUtils.isEmpty(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.EQUAL);
        return this;
    }

    /**
     * column != 'value'
     */
    public FilterPlus addNotEquals(String column, Object value) {
        if (ObjectUtils.isEmpty(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.UNEQUAL);
        return this;
    }

    /**
     * (column1 like '%value%' or column2 like '%value%')
     */
    public FilterPlus addLike(List<String> columns, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        if (!CollectionUtils.isEmpty(columns)) {
            StringJoiner filterJoiner = new StringJoiner(FormatConstant.OR);
            for (String column : columns) {
                addFilter(filterJoiner, column, value, FormatConstant.LIKE);
            }
            filter.add(String.format(FormatConstant.BRACKETS, filterJoiner));
        }
        return this;
    }

    /**
     * column1 not like '%value%' and column2 not like '%value%'
     */
    public FilterPlus addNotLike(List<String> columns, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        if (!CollectionUtils.isEmpty(columns)) {
            for (String column : columns) {
                addFilter(filter, column, value, FormatConstant.NOT_LIKE);
            }
        }
        return this;
    }

    /**
     * column like '%value%'
     */
    public FilterPlus addLike(String column, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.LIKE);
        return this;
    }

    /**
     * column not like '%value%'
     */
    public FilterPlus addNotLike(String column, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.NOT_LIKE);
        return this;
    }

    /**
     * (column1 like '%value' or column2 like '%value')
     */
    public FilterPlus addLeftLike(List<String> columns, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        if (!CollectionUtils.isEmpty(columns)) {
            StringJoiner filterJoiner = new StringJoiner(FormatConstant.OR);
            for (String column : columns) {
                addFilter(filterJoiner, column, value, FormatConstant.LEFT_LIKE);
            }
            filter.add(String.format(FormatConstant.BRACKETS, filterJoiner));
        }
        return this;
    }

    /**
     * column1 not like '%value' and column2 not like '%value'
     */
    public FilterPlus addNotLeftLike(List<String> columns, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        if (!CollectionUtils.isEmpty(columns)) {
            for (String column : columns) {
                addFilter(filter, column, value, FormatConstant.NOT_LEFT_LIKE);
            }
        }
        return this;
    }

    /**
     * column like '%value'
     */
    public FilterPlus addLeftLike(String column, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.LEFT_LIKE);
        return this;
    }

    /**
     * column not like '%value'
     */
    public FilterPlus addNotLeftLike(String column, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.NOT_LEFT_LIKE);
        return this;
    }

    /**
     * (column1 like 'value%' or column2 like 'value%')
     */
    public FilterPlus addRightLike(List<String> columns, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        if (!CollectionUtils.isEmpty(columns)) {
            StringJoiner filterJoiner = new StringJoiner(FormatConstant.OR);
            for (String column : columns) {
                addFilter(filterJoiner, column, value, FormatConstant.RIGHT_LIKE);
            }
            filter.add(String.format(FormatConstant.BRACKETS, filterJoiner));
        }
        return this;
    }

    /**
     * column1 not like 'value%' and column2 not like 'value%'
     */
    public FilterPlus addNotRightLike(List<String> columns, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        if (!CollectionUtils.isEmpty(columns)) {
            for (String column : columns) {
                addFilter(filter, column, value, FormatConstant.NOT_RIGHT_LIKE);
            }
        }
        return this;
    }

    /**
     * column like 'value%'
     */
    public FilterPlus addRightLike(String column, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.RIGHT_LIKE);
        return this;
    }

    /**
     * column not like 'value%'
     */
    public FilterPlus addNotRightLike(String column, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.NOT_RIGHT_LIKE);
        return this;
    }

    /**
     * column between startValue and endValue
     */
    public FilterPlus addBetween(String column, Object startValue, Object endValue) {
        if (ObjectUtils.isEmpty(startValue) || ObjectUtils.isEmpty(endValue)) {
            return this;
        }
        String paramName1 = getParamName();
        String paramName2 = getParamName();
        String parName1 = String.format(DaoConst.FORMAT_PARAM, paramName1);
        String parName2 = String.format(DaoConst.FORMAT_PARAM, paramName2);
        filter.add(String.format(FormatConstant.BETWEEN_AND, column, parName1, parName2));
        map.put(paramName1, startValue);
        map.put(paramName2, endValue);
        return this;
    }

    /**
     * column &gt; value
     */
    public FilterPlus addGt(String column, Object value) {
        if (ObjectUtils.isEmpty(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.GREATER_THEN);
        return this;
    }

    /**
     * column &ge; value
     */
    public FilterPlus addGte(String column, Object value) {
        if (ObjectUtils.isEmpty(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.GREATER_OR_EQUAL);
        return this;
    }

    /**
     * column &lt; value
     */
    public FilterPlus addLt(String column, Object value) {
        if (ObjectUtils.isEmpty(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.LESS_THEN);
        return this;
    }

    /**
     * column &le; value
     */
    public FilterPlus addLte(String column, Object value) {
        if (ObjectUtils.isEmpty(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.LESS_OR_EQUAL);
        return this;
    }

    /**
     * column is null
     */
    public FilterPlus addIsNull(String column) {
        filter.add(String.format(FormatConstant.IS_NULL, column));
        return this;
    }

    /**
     * column is not null
     */
    public FilterPlus addIsNotNull(String column) {
        filter.add(String.format(FormatConstant.IS_NOT_NULL, column));
        return this;
    }

    /**
     * column in (value1,value2)
     */
    public FilterPlus addIn(String column, List<? extends Object> values) {
        if (!CollectionUtils.isEmpty(values)) {
            String value = values.stream().map(p-> {
                String paramName = getParamName();
                String parName = String.format(DaoConst.FORMAT_PARAM, paramName);
                map.put(paramName, p);
                return parName;
            }).collect(Collectors.joining(FormatConstant.COMMA));
            filter.add(String.format(FormatConstant.IN, column, value));
        }
        return this;
    }

    /**
     * column not in (value1,value2)
     */
    public FilterPlus addNotIn(String column, List<? extends Object> values) {
        if (!CollectionUtils.isEmpty(values)) {
            String value = values.stream().map(p-> {
                String paramName = getParamName();
                String parName = String.format(DaoConst.FORMAT_PARAM, paramName);
                map.put(paramName, p);
                return parName;
            }).collect(Collectors.joining(FormatConstant.COMMA));
            filter.add(String.format(FormatConstant.NOT_IN, column, value));
        }
        return this;
    }

    /**
     * column regexp 'value'
     */
    public FilterPlus addRegexp(String column, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.REGEXP);
        return this;
    }

    /**
     * column in (select field from table where enabled_flag = 1 and (subFilter))
     * 
     * @param column
     * @param table
     * @param field
     * @param customer
     * @return
     */
    public FilterPlus addSubQuery(String column, String table, String field, Consumer<FilterPlus> customer) {
        return addSubQuery(column, table, field, customer, PlusEnum.AND);
    }
    
    /**
     * column in (select field from table where enabled_flag = 1 and (subFilter))
     */
    public FilterPlus addSubQuery(String column, String table, String field, Consumer<FilterPlus> customer,
            PlusEnum connector) {
        BaseDaoConfig daoConfig = CommonUtils.getDaoConfig();
        String enableFlagFieldName = daoConfig.getEnableFlag();
        String enableFlagValue = daoConfig.getEnableFlagValue();
        boolean configEnabledFlag = StringUtils.hasLength(enableFlagFieldName)
                && StringUtils.hasLength(enableFlagValue)
                && !DaoConfigContextHolder.getIncludeDisable();
        StringJoiner filterJoiner = new StringJoiner(FormatConstant.AND);
        if (configEnabledFlag) {
            String[] strs = enableFlagValue.split(DaoConst.COMMA);
            if (StringUtils.hasLength(strs[0])) {
                Object enableVal = DaoUtils.getEnableFlagValue(strs[0]);
                addFilter(filterJoiner, enableFlagFieldName, enableVal, FormatConstant.EQUAL);
            }
        }
        if (customer != null) {
            FilterPlus subFilter = new FilterPlus(connector);
            customer.accept(subFilter);
            String subParamFilter = subFilter.getParamFilter();
            if (StringUtils.hasLength(subParamFilter.replace("(", "").replace(")", ""))) {
                filterJoiner.add(subParamFilter);
                map.putAll(subFilter.getParamMap());
            }
        } else {
            filterJoiner.add("1 = 1");
        }
        filter.add(String.format(FormatConstant.OTHER_TABLE_FIELD_WHERE, column, field, table, filterJoiner));
        return this;
    }

    /**
     * column JSON_CONTAINS 'value'
     */
    public FilterPlus addJson(String column, String value) {
        if (!StringUtils.hasLength(value)) {
            return this;
        }
        addFilter(column, value, FormatConstant.JSON_CONTAINS);
        return this;
    }
}
